#!/home/shuoge/App/anaconda3/envs/MindSpore-GPU/bin/python
# To add a new cell, type '# %%'
# To add a new markdown cell, type '# %% [markdown]'
# %%
"""train resnet"""


# %%
# import things
import os
import argparse
import ast
import yaml


# %%
# import mindspore
import mindspore.nn as nn
import mindspore.common.initializer as weight_init
from mindspore import context, Tensor
from mindspore.nn.optim.momentum import Momentum
from mindspore.train.model import Model
from mindspore.context import ParallelMode
from mindspore.train.callback import ModelCheckpoint, CheckpointConfig, LossMonitor, TimeMonitor
from mindspore.nn.loss import SoftmaxCrossEntropyWithLogits
from mindspore.train.loss_scale_manager import FixedLossScaleManager
from mindspore.train.serialization import load_checkpoint, load_param_into_net
from mindspore.communication.management import init, get_rank, get_group_size
from mindspore.common import set_seed


# %%
# import src files
# from src.lr_generator import get_lr, warmup_cosine_annealing_lr
# from src.CrossEntropySmooth import CrossEntropySmooth
from src.dataset import create_dataset2 as create_dataset
from src.resnet import resnet50 as resnet
from src.lr_generator import get_lr
from src.CrossEntropySmooth import CrossEntropySmooth


# %%
# inpuit params
parser = argparse.ArgumentParser(description='Image classification')
parser.add_argument('--net', type=str, default='resnet50',
                    help='Resnet Model, either resnet50 or resnet101. Default: resnet50')
parser.add_argument('--dataset_path', required=True, type=str, default=None, help='Dataset path')
parser.add_argument('--parameter_server', type=ast.literal_eval, default=False, help='Run parameter server train')
args_opt = parser.parse_args()

# %%
# produce deterministic result
set_seed(1)


# %%
yaml_config_path = './src/config.yaml'
with open(yaml_config_path, mode='r', encoding='utf-8') as yaml_config_file:
    yaml_config = yaml.safe_load(yaml_config_file)

config = yaml_config['config2']


# %%
if __name__ == '__main__':
    # check point dir
    ckpt_save_dir = config['save_checkpoint_path']


    # %%
    # configure execution env
    # GRAPH_MODE is for static graph, PYNATIVE_MODE is for dynamic graph
    # we use GPU
    context.set_context(mode=context.GRAPH_MODE, device_target='GPU', save_graphs=False)


    # %%
    # create dataset
    dataset = create_dataset(dataset_path=args_opt.dataset_path, do_train=True, repeat_num=1, batch_size=config['batch_size'])
    step_size = dataset.get_dataset_size()


    # %%
    # define net
    net = resnet(class_num=config['class_num'])


    # %%
    # init weight
    # iterate over all cells in net
    for _, cell in net.cells_and_names():
        # if it is a conv layer
        if isinstance(cell, nn.Conv2d):
            # initializer takes in params: init (tensor, str, initializer, numbers)
            # usually use in-built initial method: He, Xavier
            cell.weight.set_data(weight_init.initializer(weight_init.XavierUniform(),
                                                            cell.weight.shape,
                                                            cell.weight.dtype))
        # if it is full connected layer
        if isinstance(cell, nn.Dense):
            cell.weight.set_data(weight_init.initializer(weight_init.TruncatedNormal(),
                                                            cell.weight.shape,
                                                            cell.weight.dtype))
        


    # %%
    # init lr
    lr = get_lr(lr_init=config["lr_init"], lr_end=config["lr_end"], lr_max=config["lr_max"],
                    warmup_epochs=config["warmup_epochs"], total_epochs=config["epoch_size"], 
                    steps_per_epoch=step_size, lr_decay_mode=config["lr_decay_mode"])

    # convert to tensor
    lr = Tensor(lr)


    # %%
    # define optimizer
    decayed_params = []
    no_decayed_params = []

    for param in net.trainable_params():
        if 'beta' not in param.name and 'gamma' not in param.name and 'bias' not in param.name:
            # beta & gamma for batchnorm
            decayed_params.append(param)
        else:
            no_decayed_params.append(param)

    group_params = [{'params': decayed_params, 'weight_decay': config["weight_decay"]},
                    {'params': no_decayed_params},
                    {'order_params': net.trainable_params()}]
    # use momentum, try adam next time
    opt = Momentum(group_params, lr, config["momentum"], loss_scale=config["loss_scale"])
    # opt = Momentum(group_params, lr, beta1=0.9, beta2=0.999, loss_scale=config["loss_scale"])


    # %%
    # define loss
    if not config["use_label_smooth"]:
        config["label_smooth_factor"] = 0.0

    # use cross entropy
    loss = CrossEntropySmooth(sparse=True, reduction="mean",
                                smooth_factor=config["label_smooth_factor"], num_classes=config["class_num"])

    opt = Momentum(filter(lambda x: x.requires_grad, net.get_parameters()), lr, config["momentum"], config["weight_decay"],
                        config["loss_scale"])

    loss_scale = FixedLossScaleManager(config["loss_scale"], drop_overflow_update=False)

    # Mixed precision mdodel
    model = Model(net, loss_fn=loss, optimizer=opt, loss_scale_manager=loss_scale, metrics={'acc'},
                    amp_level="O2", keep_batchnorm_fp32=True)
                    


    # %%
    # define callbacks
    # monitor time during training
    time_cb = TimeMonitor(data_size=step_size)

    # monitor loss
    loss_cb = LossMonitor()

    cb = [time_cb, loss_cb]

    if config["save_checkpoint"]:
        config_ck = CheckpointConfig(save_checkpoint_steps=config["save_checkpoint_epochs"] * step_size,
                                    keep_checkpoint_max=config["keep_checkpoint_max"])
        ckpt_cb = ModelCheckpoint(prefix="resnet", directory=ckpt_save_dir, config=config_ck)
        cb += [ckpt_cb]


    # %%
    # train model
    if args_opt.net == "se-resnet50":
        config["epoch_size"] = config["train_epoch_size"]

    # sink when GPU or static graph mode
    model.train(config["epoch_size"] - config["pretrain_epoch_size"], dataset, callbacks=cb,
                dataset_sink_mode=True)


